/*******************************************************************************
 * Copyright (c) 2009 Kevin Bierhoff and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Kevin Bierhoff - initial API and implementation
 *******************************************************************************/
package com.googlecode.twelfdevtools.server;

import java.io.IOException;
import java.io.LineNumberReader;
import java.io.Reader;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class TwelfOutputProcessor implements Runnable {
	
	private final LineNumberReader twelfOutput;
	public String curFile;
	private String buffer;
	protected TwelfOutputProcessorState curState; 
	protected final ParsingState parsingState = new ParsingState();
	protected final WaitingState waitingState = new WaitingState();
	private List<TwelfServerListener> listeners = new LinkedList<TwelfServerListener>();
	
	public TwelfOutputProcessor(Reader twelfOutput) throws IOException {
		this.twelfOutput = new LineNumberReader(twelfOutput);
		this.curState = waitingState;
	}

	public void run() {
		String line;
		try {
			while((line = twelfOutput.readLine()) != null) {
				publishLine(line);
				curState = curState.processLine(line);
			}
			// attempt orderly shutdown
			twelfOutput.close();
		} 
		catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public void addTwelfServerListener(TwelfServerListener l) {
		synchronized(listeners) {
			listeners.add(l);
		}
	}
	
	public void removeTwelfServerListener(TwelfServerListener l) {
		synchronized(listeners) {
			listeners.remove(l);
		}
	}
	
	private void publishLine(String line) {
		TwelfServerEvent e = TwelfServerEvent.createRawLineEvent(curFile, line);
		publish(e);
	}

	private void publish(TwelfServerEvent e) {
		synchronized(listeners) {
			// iterate atomically through all listeners
			for(TwelfServerListener l : listeners) {
				try {
					l.notifyTwelfServerEvent(e);
				}
				catch(Throwable t) {
					t.printStackTrace();
				}
			}
		}
	}

	protected interface TwelfOutputProcessorState {
		TwelfOutputProcessorState processLine(String line);
	}
	
	protected static final Pattern openFile = Pattern.compile("\\[Opening file (\\S*)\\]");
	protected static final Pattern closeFile = Pattern.compile("\\[Closing file (\\S*)\\]");
	
	protected class WaitingState implements TwelfOutputProcessorState {
		
		private Matcher openFileMatcher = openFile.matcher("");
		
		public TwelfOutputProcessorState processLine(String line) {
			openFileMatcher.reset(line);
			if(openFileMatcher.matches()) {
				TwelfOutputProcessor.this.openFile(openFileMatcher.group(1));
				return TwelfOutputProcessor.this.parsingState;
			}
			// ignore...
			return this;
		}
		
	}
	
	protected class ParsingState implements TwelfOutputProcessorState {
		
		private Matcher closeFileMatcher = closeFile.matcher("");

		public TwelfOutputProcessorState processLine(String line) {
			if(line.contains("Error:")) {
				// error in input
				TwelfOutputProcessor.this.reset();
				return TwelfOutputProcessor.this.waitingState;
			}
			
			if("%% OK %%".equals(line)) {
				// unexpected end of output
				TwelfOutputProcessor.this.reset();
				return TwelfOutputProcessor.this.waitingState;
			}
			if("%% ABORT %%".equals(line)) {
				// unexpected end of output
				TwelfOutputProcessor.this.reset();
				return TwelfOutputProcessor.this.waitingState;
			}
			
			closeFileMatcher.reset(line);
			if(closeFileMatcher.matches()) {
				// should be the file we opened
				assert closeFileMatcher.group(1).equals(TwelfOutputProcessor.this.curFile);
				TwelfOutputProcessor.this.reset();
				return TwelfOutputProcessor.this.waitingState;
			}
			
			// assume this is parseable Twelf output
			if(line.startsWith("%")) {
				// special
				TwelfOutputProcessor.this.pragma(line);
			}
			else if(line.endsWith(".")) {
				// (end of) LF constant
				TwelfOutputProcessor.this.finishConstant(line);
			}
			else {
				// part of LF constant
				TwelfOutputProcessor.this.buffer(line);
			}
			// continue parsing
			return this;
		}
		
	}

	protected void reset() {
		buffer = null;
		if(curFile != null) {
			publish(TwelfServerEvent.createCloseFileEvent(curFile));
		}
		curFile = null;
	}

	public void openFile(String fileName) {
		if(curFile != null)
			// not a good sign, but keep going
			reset();
		curFile = fileName;
		publish(TwelfServerEvent.createOpenFileEvent(curFile));
	}

	protected void buffer(String line) {
		if(buffer == null)
			buffer = line;
		else
			buffer += ('\n' + line);
	}

	protected void finishConstant(String line) {
		buffer(line);
		publishConstant();
	}

	private void publishConstant() {
		assert buffer != null;
//		System.out.println("Publish (" + curFile + "): " + buffer);
		publish(TwelfServerEvent.createConstantDefinitionEvent(curFile, buffer));
		buffer = null;
	}

	protected void pragma(String line) {
		assert buffer == null;
		buffer = null;
//		System.out.println("Pragma (" + curFile + "): " + line);
		publish(TwelfServerEvent.createPragmaEvent(curFile, line));
	}

}
